package com.ruoyi.web.core.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

@Component
public class NettyServer {

    /**
     * 消息体的最大长度
     */
    private static final int MAX_FRAME_LENGTH = 1024 * 1024;

    /**
     * Message类中的length的长度，int占4位
     */
    private static final int LENGTH_FIELD_LENGTH = 4;

    /**
     * 偏移多少位之后才是我们的消息体
     */
    private static final int LENGTH_FIELD_OFFSET = 1;

    /**
     * 该字段加长度字段等于数据帧的长度，一般数据帧长度都是这样定义(即我们在设置Message中的length属性)，加入你的消息体是20位，再加上
     */
    private static final int LENGTH_ADJUSTMENT = 0;

    /**
     * 就是24位，所以在此处为了正确的解析出消息体，需要偏移4位才能解析出消息体的正确位置，我们在发送的消息里面设置的就是消息体本身的长度，所以无需偏移
     */
    private static final int INITIAL_BYTES_TO_STRIP = 0;

    /**
     * boss 线程组用于处理连接工作
     */
    private EventLoopGroup boss = new NioEventLoopGroup();
    /**
     * work 线程组用于数据处理
     */
    private EventLoopGroup work = new NioEventLoopGroup();

    public static final Logger LOGGER = LoggerFactory.getLogger(NettyServer.class);

    @Value("${netty.port}")
    private Integer port;

    /**
     * 启动Netty Server
     *
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, work)
                // 指定Channel
                .channel(NioServerSocketChannel.class)
                //使用指定的端口设置套接字地址
                .localAddress(new InetSocketAddress(port))

                //服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
                .option(ChannelOption.SO_BACKLOG, 1024)

                //设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                .childOption(ChannelOption.SO_KEEPALIVE, true)

                .childHandler(new ServerChannelInitializer());
        ChannelFuture future = bootstrap.bind().sync();
        if (future.isSuccess()) {
            LOGGER.info("启动 Netty Server 端口 {}", port );
        }
    }

    @PreDestroy
    public void destory() throws InterruptedException {
        boss.shutdownGracefully().sync();
        work.shutdownGracefully().sync();
        LOGGER.info("关闭Netty");
    }
}
